package com.symaster.common.swing;

import java.util.Arrays;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @author yinmiao
 * @version 2023/11/8
 */
public class FpsManager implements Runnable {

    private ThreadPoolExecutor threadPool;
    private final double[] times;
    private Runnable runnable;
    private double maxFps;

    public FpsManager() {
        times = new double[30];
        Arrays.fill(times, -1);
    }

    private void paintComplete() {
        System.arraycopy(times, 1, times, 0, times.length - 1);
        times[times.length - 1] = nowHighPrecisionTime();
    }

    public void paintRunnable(double maxFps, Runnable runnable) {
        this.maxFps = maxFps;
        this.runnable = runnable;
        if (this.threadPool == null) {
            initThreadPool();
        }

        if (times[times.length - 1] > 0) {
            threadPool.execute(this);
        } else {
            run(runnable);
        }
    }

    private void initThreadPool() {
        threadPool = new ThreadPoolExecutor(1, 1, 1000, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(), new ThreadPoolExecutor.DiscardOldestPolicy());
    }

    public void paintWait(double maxFps) {
        if (times[times.length - 1] > 0) {
            double v = nowHighPrecisionTime() - times[times.length - 1];
            double v1 = 1000.0 / maxFps - v;
            if (v1 > 0) {
                try {
                    TimeUnit.NANOSECONDS.sleep(Math.round(v1 * 1000000.0));
                    paintComplete();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            } else {
                paintComplete();
            }
        } else {
            paintComplete();
        }
    }

    private void run(Runnable runnable) {
        runnable.run();
        paintComplete();
    }

    public float getFps() {

        if (times[0] < 0) {
            return 0f;
        }

        double consume = (times[times.length - 1] - times[0]) / times.length; // 表示渲染一帧所耗时
        return (float) (1000 / consume);

        // int recordIndex = -1;
        // for (int i = 0; i < times.length; i++) {
        //     if (times[i] > 0) {
        //         recordIndex = i;
        //         break;
        //     }
        // }
        //
        // if (recordIndex < 0) {
        //     return 0f;
        // }
        //
        // if (recordIndex > times.length - 2) {
        //     return 0f;
        // }
        //
        // double consume = (times[times.length - 1] - times[recordIndex]) / (times.length - recordIndex - 1); // 表示渲染一帧所耗时
        // return (float) (1000 / consume);
    }

    private double nowHighPrecisionTime() {
        return System.nanoTime() / 1000000.0;
    }

    @Override
    public void run() {
        if (runnable == null) {
            return;
        }

        double v = nowHighPrecisionTime() - times[times.length - 1];
        double v1 = 1000.0 / maxFps - v;
        if (v1 > 0) {
            try {
                TimeUnit.NANOSECONDS.sleep(Math.round(v1 * 1000000.0));
                run(runnable);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        } else {
            run(runnable);
        }
    }
}
